const TextureLoader = new THREE.TextureLoader()
const GLTFLoader = new THREE.GLTFLoader()
const raycaster = new THREE.Raycaster()
const clock = new THREE.Clock()

let scene                                   // 场景
let floor                                   // 地板
let goodsGroup                              // 商品组
let labelRenderer                           // 2d标签渲染器
let pmremGenerator


Vue.createApp({
  data() {
    return {
      camera: null,                         // 相机
      renderer: null,                       // 渲染器
      controls: null,                       // 视图控制
      characterControls: null,              // 第三人称角色控制器
      keysPressed: {},                      // 输入
      pointer: new THREE.Vector2(),         // 鼠标指针
      selectedModel: null,                  // 已选模型
      goodsModelList: [{
        path: 'models/gltf/chair.glb',
        name: '椅子模型',
        desc: 'The aim of the project is to create an easy to use, lightweight, cross-browser, general purpose 3D library. The current builds only include a WebGL renderer but WebGPU (experimental), SVG and CSS3D renderers are also available in the examples.',
        // 测试环境贴图
        texture: {
          path: 'textures/venice_sunset_1k.hdr',
          type: 'envMap'
        },
        position: {
          x: 2,
          y: 0,
          z: -2
        }
      }, {
        path: 'models/gltf/airplane.glb',
        name: '飞机模型',
        desc: 'The aim of the project is to create an easy to use, lightweight, cross-browser, general purpose 3D library. The current builds only include a WebGL renderer but WebGPU (experimental), SVG and CSS3D renderers are also available in the examples.',
        texture: {
          path: 'textures/quarry_01_1k.hdr',
          type: 'envMap'
        },
        position: {
          x: -5,
          y: 0,
          z: -10
        }
      }],
      showDialog: {
        show: false,
        data: {}
      }
    }
  },
  mounted() {

    this.init()

    // 自适应窗口
    window.addEventListener('resize', () => {
      this.camera.aspect = window.innerWidth / window.innerHeight
      this.camera.updateProjectionMatrix()
      this.renderer.setSize(window.innerWidth, window.innerHeight)
      labelRenderer.setSize(window.innerWidth, window.innerHeight)
    })

    // 监听键盘事件
    document.addEventListener('keydown', (event) => {
      if (this.showDialog.show) return
      this.keysPressed[event.key.toLowerCase()] = true
    }, false)
    document.addEventListener('keyup', (event) => {
      if (this.showDialog.show) return
      this.keysPressed[event.key.toLowerCase()] = false
    }, false)

    // 监听鼠标移动
    document.addEventListener('mousemove', (event) => {
      this.pointer.x = (event.clientX / window.innerWidth) * 2 - 1
      this.pointer.y = - (event.clientY / window.innerHeight) * 2 + 1
    })

  },
  watch: {
    'showDialog.show'(n) {
      this.controls.enabled = !n
    }
  },
  methods: {
    init() {
      this._initBase()
      this._initFloor()
      this._initPerson()
      this._initGoodsGroup()
      this._initTv()
      // 渲染动画
      this.animate()
    },
    _initBase() {
      // 初始化场景
      scene = new THREE.Scene()

      // 初始化相机
      const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
      camera.position.y = 2
      camera.position.z = 2
      camera.filmGauge = 45
      this.camera = camera
      scene.add(this.camera)

      // 添加环境光
      const ambientLight = new THREE.AmbientLight(0xfefefe)
      scene.add(ambientLight)

      // 初始主要照明平行光源
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
      directionalLight.position.y = 70
      directionalLight.position.x = 100
      directionalLight.position.z = 0
      directionalLight.castShadow = true
      scene.add(directionalLight)

      // 初始化渲染器
      const renderer = new THREE.WebGLRenderer()
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.shadowMap.enabled = true
      this.renderer = renderer
      document.body.appendChild(this.renderer.domElement)

      // 初始化视图控制
      this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement)

      // 天空盒
      // new THREE.RGBELoader().load('textures/quarry_01_1k.hdr', (texture) => {
      //   texture.mapping = THREE.EquirectangularReflectionMapping
      //   scene.background = texture
      //   scene.environment = texture
      // })

      // 商品组
      goodsGroup = new THREE.Group()
      scene.add(goodsGroup)


      // label标签
      labelRenderer = new THREE.CSS2DRenderer()
      labelRenderer.setSize(window.innerWidth, window.innerHeight)
      labelRenderer.domElement.style.position = 'absolute'
      labelRenderer.domElement.style.top = '0px'
      labelRenderer.domElement.style.pointerEvents = 'none'
      document.body.appendChild(labelRenderer.domElement)
    },
    // 初始化地板
    _initFloor() {
      const planeGeometry = new THREE.PlaneGeometry(100, 100);
      const floorMaterial = new THREE.MeshStandardMaterial({
        roughness: 1,
        color: 0xffffff,
        metalness: 0.1,
        map: TextureLoader.load("textures/Terrazzo_Tiles_001_SD/Terrazzo_Tiles_001_basecolor.jpg", (map) => {
          map.wrapS = THREE.RepeatWrapping
          map.wrapT = THREE.RepeatWrapping
          map.encoding = THREE.sRGBEncoding;
          map.repeat.set(30, 30)
        }),
        aoMap: TextureLoader.load("textures/Terrazzo_Tiles_001_SD/Terrazzo_Tiles_001_ambientOcclusion.jpg", (map) => {
          map.wrapS = THREE.RepeatWrapping
          map.wrapT = THREE.RepeatWrapping
          map.repeat.set(30, 30)
        }),
        normalMap: TextureLoader.load("textures/Terrazzo_Tiles_001_SD/Terrazzo_Tiles_001_normal.jpg", (map) => {
          map.wrapS = THREE.RepeatWrapping
          map.wrapT = THREE.RepeatWrapping
          map.repeat.set(30, 30)
        }),
        roughnessMap: TextureLoader.load("textures/Terrazzo_Tiles_001_SD/Terrazzo_Tiles_001_roughness.jpg", (map) => {
          map.wrapS = THREE.RepeatWrapping
          map.wrapT = THREE.RepeatWrapping
          map.repeat.set(30, 30)
        }),
        side: THREE.DoubleSide
      })
      floor = new THREE.Mesh(planeGeometry, floorMaterial)
      floor.receiveShadow = true
      floor.rotation.x = Math.PI / 2
      floor.position.y = 0
      scene.add(floor)
    },
    // 加载角色
    _initPerson() {
      this.loadModel('models/gltf/Xbot.glb').then((gltf) => {
        const personModel = gltf.scene
        personModel.traverse((object) => {
          if (object.isMesh) object.castShadow = true
        })
        scene.add(personModel)

        // 添加模型骨骼
        const skeleton = new THREE.SkeletonHelper(personModel)
        // 隐藏骨骼
        skeleton.visible = false
        scene.add(skeleton)

        // 获取模型动画
        const animations = gltf.animations
        const personAnimationMixer = new THREE.AnimationMixer(personModel);
        // 站立动画
        const idleAction = personAnimationMixer.clipAction(animations[2])
        // 走路动画
        const walkAction = personAnimationMixer.clipAction(animations[6])
        // 初始化人物控制
        this.characterControls = new CharacterControls(personModel, personAnimationMixer, {
          idle: idleAction,
          walk: walkAction
        }, this.controls, this.camera, 'idle')
      })
    },
    // 加载商品模型
    _initGoodsGroup() {
      pmremGenerator = new THREE.PMREMGenerator(this.renderer)
      pmremGenerator.compileEquirectangularShader()

      THREE.DefaultLoadingManager.onLoad = () => {
        pmremGenerator.dispose()
      }

      Promise.all(
        this.goodsModelList.map(v => this.loadModel(v.path))
      ).then((models) => {
        models.forEach((gltf, k) => {
          const goodsModel = gltf.scene
          const { x, y, z } = this.goodsModelList[k].position
          goodsModel.position.x = x
          goodsModel.position.y = y
          goodsModel.position.z = z
          goodsModel.traverse((object) => {
            if (object.isMesh) {
              object.castShadow = true
              object.receiveShadow = true
            }
          })
          goodsGroup.add(goodsModel)
          const goodsModelMesh = goodsModel.children[0]
          const hasTexture = this.goodsModelList[k].texture
          if (hasTexture) {
            const { type, path } = hasTexture
            new THREE.RGBELoader().load(path, (texture) => {

              goodsModelMesh.children.forEach(mesh => {
                // 加载环境贴图
                if (type === 'envMap') {
                  const renderTarget = pmremGenerator.fromEquirectangular(texture)
                  const envMap = renderTarget.texture
                  mesh.material.envMap = envMap
                }
                mesh.material.needsUpdate = true
              })
            })
          }
          this.goodsModelList[k].id = goodsModel.uuid
        })
        this.addLabelsForModel()
      })
    },
    // 为模型添加标签
    addLabelsForModel() {
      goodsGroup.children.forEach((v, k) => {
        const labelDom = document.createElement('div')
        labelDom.className = 'model_label'
        labelDom.id = this.goodsModelList[k].id
        labelDom.textContent = this.goodsModelList[k].name
        labelDom.style.pointerEvents = 'auto'
        // 点击标签，打开信息弹窗
        labelDom.onclick = () => {
          this.showDialog.data = this.goodsModelList[k]
          this.showDialog.show = true
        }
        const label = new THREE.CSS2DObject(labelDom)
        label.position.set(0, 0, 0)
        v.add(label)
        label.layers.set(0)
      })
    },
    // 初始化电视
    _initTv() {
      this.loadModel('models/gltf/tv.glb').then((gltf) => {
        const tvModel = gltf.scene
        tvModel.position.x = 4
        tvModel.position.y = 0
        tvModel.position.z = -4.001
        tvModel.traverse((object) => {
          if (object.isMesh) {
            object.castShadow = true
            object.receiveShadow = true
          }
        })
        // 电视机添加视频贴图
        const video = document.createElement('video')
        video.muted = true
        video.loop = true
        video.controls = true
        // video.playsInline = true
        video.autoplay = true
        video.src = './textures/sintel.mp4'
        video.play()

        const videoTexture = new THREE.VideoTexture(video)
        videoTexture.matrixAutoUpdate = false
        videoTexture.wrapS = THREE.RepeatWrapping
        videoTexture.wrapT = THREE.RepeatWrapping

        const screenGeometry = new THREE.PlaneGeometry(1.97, 1.12)
        const screenMaterial = new THREE.MeshBasicMaterial({
          color: 0xffffff,
          side: THREE.DoubleSide,
          map: videoTexture
        })
        const screen = new THREE.Mesh(screenGeometry, screenMaterial)
        screen.position.z = -4
        screen.position.x = 4
        screen.position.y = 0.64
        scene.add(screen)

        scene.add(tvModel)
      })
    },
    animate() {
      const delta = clock.getDelta()
      if (this.characterControls) {
        this.characterControls.update(delta, this.keysPressed)
      }
      this.controls.update()
      this.intersect()
      this.renderer.render(scene, this.camera)
      labelRenderer.render(scene, this.camera)
      requestAnimationFrame(this.animate)
    },
    // 射线选择
    intersect() {
      raycaster.setFromCamera(this.pointer, this.camera)
      const intersects = raycaster.intersectObjects(goodsGroup.children, true)
      if (intersects.length > 0 && !this.showDialog.show) {
        if (this.selectedModel != intersects[0].object) {
          if (this.selectedModel) {
            this.selectedModel.material.transparent = true
            this.selectedModel.material.opacity = 1
          }
          this.selectedModel = intersects[0].object
          this.selectedModel.material.opacity = .7
        }
      } else {
        if (this.selectedModel) {
          this.selectedModel.material.transparent = true
          this.selectedModel.material.opacity = 1
        }
        this.selectedModel = null
      }
    },
    // 加载模型
    loadModel(modelPath) {
      return new Promise((resolve, reject) => {
        try {
          GLTFLoader.load(modelPath, (gltf) => {
            resolve(gltf)
          })
        } catch (error) {
          console.log('模型加载出错...', error)
          reject(error)
        }
      })
    }
  }
}).mount('#app')